#include <stdio.h>
#include <unistd.h>
#include <stdbool.h>
#include <alsa/asoundlib.h>

bool debug_msg(int result, const char *str);
void open_music_file(const char *path_name);
void pcm_init(void);
void play_music(void);

snd_pcm_t *handle;						// 控制句柄
snd_pcm_hw_params_t *params;			// 参数结构体
snd_pcm_uframes_t frames = 1024;		// 一个周期的帧数
snd_pcm_access_t access_mode = SND_PCM_ACCESS_MMAP_INTERLEAVED ;// 访问模式:交错访问
snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE;				// 采样位数:16位,小端存储
int channel = 1;						// 通道数（单声道设为1，立体双声道为2）
unsigned int simple_rate = 44100;		// 采样率

FILE *fp;			// 音乐文件指针变量
char *buff = NULL;	// 缓冲区指针
int buffer_size;	// 缓冲区大小

int ret;	// 记录函数返回值
// 定义WAV格式结构体
struct WAV_HEADER
{
	char 		chunk_id[4]; 	// riff 标志号
	uint32_t 	chunk_size; 	// riff长度
	char 		format[4]; 		// 格式类型(wav)
	
	char 		sub_chunk1_id[4]; 	// fmt 格式块标识
	uint32_t 	sub_chunk1_size; 	// fmt 长度 格式块长度。
	uint16_t 	audio_format; 		// 编码格式代码							常见的 WAV 文件使用 PCM 脉冲编码调制格式,该数值通常为 1
	uint16_t 	num_channels; 		// 声道数 								单声道为 1,立体声或双声道为 2
	uint32_t  	sample_rate; 		// 采样频率 							每个声道单位时间采样次数。常用的采样频率有 11025, 22050 和 44100 kHz。
	uint32_t 	byte_rate; 			// 传输速率 							该数值为:声道数×采样频率×每样本的数据位数/8。播放软件利用此值可以估计缓冲区的大小。
	uint16_t	block_align; 		// 数据块对齐单位						采样帧大小。该数值为:声道数×位数/8。播放软件需要一次处理多个该值大小的字节数据,用该数值调整缓冲区。
	uint16_t 	bits_per_sample; 	// 采样位数								存储每个采样值所用的二进制数位数。常见的位数有 4、8、12、16、24、32

	char 		sub_chunk2_id[4]; 	
	uint32_t 	sub_chunk2_size; 	// 音频数据大小
} wav_header;

int main(int argc, char *argv [])
{
	if((ret = getopt(argc,argv,"m:")) == 'm'){
		open_music_file(optarg);
	} else {
		printf("argument error!\n");
		return 0;
	}
	pcm_init();
	
	play_music();	// 播放音乐
	
	fclose(fp);
    snd_pcm_drain(handle);
    snd_pcm_close(handle);
	return 0;
}

void play_music(void)
{
	int writed_size;	// 记录已写入的数据量
	printf("Start playing.............\n");
    do{
		if((writed_size + (frames*2)) > buffer_size){
			ret =  snd_pcm_mmap_writei(handle, buff, (buffer_size-writed_size)/2);
			printf("playback ended!\n");
			break;
		}
		else{
			ret =  snd_pcm_mmap_writei(handle, buff, frames);
		}
		buff += (ret * 2);			// 修改音频数据写入位置
		writed_size += (ret * 2);	// 记录已写入的音频数据字节个数
        if( ret>0 && ret<frames ){
            printf("short write\n");
        }else if(ret == -EPIPE){
            printf("underrun occurred\n");
            snd_pcm_prepare(handle);
        }else if (ret < 0){
            printf("error from writei: %s\n",snd_strerror(ret));
            break;
        }
    }while(1);
}

// 用于根据错误码解析错误原因
bool debug_msg(int result, const char *str)
{
	if(result < 0){
		printf("err: %s error!, result = %d, err_info = %s \n", str, result, snd_strerror(result));
		exit(1);
	}
	return true;
}
// 打开音频文件并读取音频数据
void open_music_file(const char *path_name)
{
	int wav_header_size; 	// 接收wav_header数据结构体的大小
	fp = fopen(path_name, "rb");
	if(fp == NULL){
		printf("music file is NULL \n");
		fclose(fp);
		exit(1);
	}
	// 把文件指针定位到文件的开头处
	fseek(fp, 0, SEEK_SET);
	// 读取文件，并解析文件头获取有用信息
	wav_header_size = fread(&wav_header, 1, sizeof(wav_header), fp);
	printf("wav文件头结构体大小：	%d 			\n", wav_header_size);
	printf("RIFF标志：		\t 		%c%c%c%c 	\n", wav_header.chunk_id[0], wav_header.chunk_id[1], wav_header.chunk_id[2], wav_header.chunk_id[3]);
	printf("文件大小： 		\t 		%d			\n", wav_header.chunk_size);
	printf("文件格式： 		\t 		%c%c%c%c 	\n", wav_header.format[0], wav_header.format[1], wav_header.format[2], wav_header.format[3]);
	printf("格式块标识：	\t\t\t 	%c%c%c%c	\n", wav_header.sub_chunk1_id[0], wav_header.sub_chunk1_id[1], wav_header.sub_chunk1_id[2], wav_header.sub_chunk1_id[3]);
	printf("格式块长度：	\t\t\t 	%d 			\n", wav_header.sub_chunk1_size);
	printf("编码格式代码: 	\t\t\t 	%d 			\n", wav_header.audio_format);
	printf("声道数:			\t 		%d			\n", wav_header.num_channels);
	printf("采样频率:		\t 		%d			\n", wav_header.sample_rate);
	printf("传输速率： 		\t\t 	%d 			\n", wav_header.byte_rate);
	printf("数据块对齐单位: \t\t\t 	%d			\n", wav_header.block_align);
	printf("采样位数(长度):		\t 		%d			\n", wav_header.bits_per_sample);
	// 根据音频数据块大小创建缓冲区
	buffer_size = wav_header.sub_chunk2_size;
	buff = (char *)malloc(buffer_size);
	// 以1个字节为单位从fp文件中读取buffer_size个字节数据放到缓存中
	ret = fread(buff, 1, buffer_size, fp);
	if(ret == 0){
		printf("end of music file input! \n");
		exit(1);
	}
	if(ret < 0){
		printf("read pcm from file! \n");
		exit(1);
	}
}
void pcm_init(void)
{
	char *pcm_name;	
	/* 获取设备名称并打开pcm设备 */
	pcm_name = strdup("plughw:0,0");	
    debug_msg(snd_pcm_open(&handle, pcm_name,SND_PCM_STREAM_PLAYBACK, 0),"打开pcm设备");
    /* 分配一个硬件参数对象 */
    debug_msg(snd_pcm_hw_params_malloc(&params),"给硬件参数分配空间");
    debug_msg(snd_pcm_hw_params_any(handle, params),"初始化参数");		/* 使用默认值填充参数对象. */
    /* 设置硬件参数 */
    debug_msg(snd_pcm_hw_params_set_access(handle, params,access_mode),"设置模式");	/* 非交错模式 non Interleaved mode */
    debug_msg(snd_pcm_hw_params_set_format(handle, params,format),"设置采样位数");	/* 采样位数 Signed 16-bit little-endian format */
    debug_msg(snd_pcm_hw_params_set_channels(handle, params, channel),"设置通道数");/* 通道数 one channels  */
    debug_msg(snd_pcm_hw_params_set_rate_near(handle, params,&simple_rate, NULL),"设置采样率");/* 采样率 44100 bits/second sampling rate (CD quality) */
    debug_msg(snd_pcm_hw_params_set_period_size_near(handle,params, &frames, NULL),"设置周期帧数");/* 周期帧数一般为 1024 */
    debug_msg(snd_pcm_hw_params(handle, params),"将设置参数写入驱动");
}